From ca71119a40ac9b196700855dac6484f4e198bdb1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jonas=20=C3=85dahl?= Date: Sun, 16 Feb 2020 12:59:24 +0100 Subject: [PATCH] gdk/surface: Replace move_to_rect() with GdkPopupLayout based API Replace the gdk_surface_move_to_rect() API with a new GdkSurface method called gdk_surface_present_popup() taking a new GdkPopupLayout object describing how they should be laid out on screen. The layout properties provided are the same as the ones used with gdk_surface_move_to_rect(), except they are now set up using GdkPopupLayout. Calling gdk_surface_present_popup() will either show the popup at the position described using the popup layout object and a new unconstrained size, or reposition it accordingly. In some situations, such as when a popup is set to autohide, presenting may immediately fail, in case the grab was not granted by the display server. After a successful present, the result of the layout can be queried using the following methods: * gdk_surface_get_position() - to get the position relative to its parent * gdk_surface_get_width() - to get the current width * gdk_surface_get_height() - to get the current height * gdk_surface_get_rect_anchor() - to get the anchor point on the anchor rectangle the popup was effectively positioned against given constraints defined by the environment and the layout rules provided via GdkPopupLayout. * gdk_surface_get_surface_anchor() - the same as the one above but for the surface anchor. A new signal replaces the old "moved-to-rect" one - "popup-layout-changed". However, it is only intended to be emitted when the layout changes implicitly by the windowing system, for example if the monitor resolution changed, or the parent window moved. --- docs/reference/gdk/gdk4-sections.txt | 4 +- gdk/broadway/gdksurface-broadway.c | 86 ++- gdk/gdk-autocleanup.h | 1 + gdk/gdkpopuplayout.c | 281 ++++++++++ gdk/gdkpopuplayout.h | 136 +++++ gdk/gdksurface.c | 295 +++++----- gdk/gdksurface.h | 57 +- gdk/gdksurfaceprivate.h | 93 +++- gdk/meson.build | 2 + gdk/quartz/gdksurface-quartz.c | 90 ++- gdk/wayland/gdkdevice-wayland.c | 2 - gdk/wayland/gdksurface-wayland.c | 794 +++++++++++++++++---------- gdk/win32/gdksurface-win32.c | 84 ++- gdk/x11/gdksurface-x11.c | 86 ++- gtk/gtkpopover.c | 299 +++++++--- gtk/gtktooltipwindow.c | 62 ++- 16 files changed, 1648 insertions(+), 724 deletions(-) create mode 100644 gdk/gdkpopuplayout.c create mode 100644 gdk/gdkpopuplayout.h diff --git a/docs/reference/gdk/gdk4-sections.txt b/docs/reference/gdk/gdk4-sections.txt index 48eadea63b..9e9cb5bb24 100644 --- a/docs/reference/gdk/gdk4-sections.txt +++ b/docs/reference/gdk/gdk4-sections.txt @@ -205,7 +205,9 @@ gdk_surface_set_keep_above gdk_surface_set_keep_below gdk_surface_set_opacity gdk_surface_resize -gdk_surface_move_to_rect +gdk_surface_present_popup +gdk_surface_get_popup_rect_anchor +gdk_surface_get_popup_surface_anchor gdk_surface_raise gdk_surface_lower gdk_surface_restack diff --git a/gdk/broadway/gdksurface-broadway.c b/gdk/broadway/gdksurface-broadway.c index 0f2411f63a..af946eef71 100644 --- a/gdk/broadway/gdksurface-broadway.c +++ b/gdk/broadway/gdksurface-broadway.c @@ -455,18 +455,21 @@ gdk_broadway_surface_move (GdkSurface *surface, } static void -gdk_broadway_surface_moved_to_rect (GdkSurface *surface, - GdkRectangle final_rect) +gdk_broadway_surface_layout_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { - GdkSurface *toplevel; + GdkRectangle final_rect; int x, y; - if (surface->surface_type == GDK_SURFACE_POPUP) - toplevel = surface->parent; - else - toplevel = surface->transient_for; + gdk_surface_layout_popup_helper (surface, + width, + height, + layout, + &final_rect); - gdk_surface_get_origin (toplevel, &x, &y); + gdk_surface_get_origin (surface->parent, &x, &y); x += final_rect.x; y += final_rect.y; @@ -474,8 +477,10 @@ gdk_broadway_surface_moved_to_rect (GdkSurface *surface, final_rect.height != surface->height) { gdk_broadway_surface_move_resize (surface, - x, y, - final_rect.width, final_rect.height); + x, + y, + final_rect.width, + final_rect.height); } else { @@ -484,22 +489,49 @@ gdk_broadway_surface_moved_to_rect (GdkSurface *surface, } static void -gdk_broadway_surface_move_to_rect (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy) -{ - gdk_surface_move_to_rect_helper (surface, - rect, - rect_anchor, - surface_anchor, - anchor_hints, - rect_anchor_dx, - rect_anchor_dy, - gdk_broadway_surface_moved_to_rect); +show_popup (GdkSurface *surface) +{ + gdk_surface_raise (surface); + gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0); + _gdk_surface_update_viewable (surface); + gdk_broadway_surface_show (surface, FALSE); + gdk_surface_invalidate_rect (surface, NULL); +} + +static void +show_grabbing_popup (GdkSeat *seat, + GdkSurface *surface, + gpointer user_data) +{ + show_popup (surface); +} + +static gboolean +gdk_broadway_surface_present_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) +{ + gdk_broadway_surface_layout_popup (surface, width, height, layout); + + if (GDK_SURFACE_IS_MAPPED (surface)) + return TRUE; + + if (surface->autohide) + { + gdk_seat_grab (gdk_display_get_default_seat (surface->display), + surface, + GDK_SEAT_CAPABILITY_ALL, + TRUE, + NULL, NULL, + show_grabbing_popup, NULL); + } + else + { + show_popup (surface); + } + + return GDK_SURFACE_IS_MAPPED (surface); } static void @@ -1393,7 +1425,7 @@ gdk_broadway_surface_class_init (GdkBroadwaySurfaceClass *klass) impl_class->lower = gdk_broadway_surface_lower; impl_class->restack_toplevel = gdk_broadway_surface_restack_toplevel; impl_class->toplevel_resize = gdk_broadway_surface_toplevel_resize; - impl_class->move_to_rect = gdk_broadway_surface_move_to_rect; + impl_class->present_popup = gdk_broadway_surface_present_popup; impl_class->get_geometry = gdk_broadway_surface_get_geometry; impl_class->get_root_coords = gdk_broadway_surface_get_root_coords; impl_class->get_device_state = gdk_broadway_surface_get_device_state; diff --git a/gdk/gdk-autocleanup.h b/gdk/gdk-autocleanup.h index 513725620a..0086e21794 100644 --- a/gdk/gdk-autocleanup.h +++ b/gdk/gdk-autocleanup.h @@ -35,6 +35,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkGLContext, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkKeymap, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkMonitor, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkSeat, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkPopupLayout, gdk_popup_layout_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkVulkanContext, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkSurface, g_object_unref) diff --git a/gdk/gdkpopuplayout.c b/gdk/gdkpopuplayout.c new file mode 100644 index 0000000000..7bc7704c64 --- /dev/null +++ b/gdk/gdkpopuplayout.c @@ -0,0 +1,281 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 2020 Red Hat + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#include "config.h" + +#include "gdkpopuplayout.h" + +#include "gdksurface.h" + +struct _GdkPopupLayout +{ + /* < private >*/ + grefcount ref_count; + + GdkRectangle anchor_rect; + GdkGravity rect_anchor; + GdkGravity surface_anchor; + GdkAnchorHints anchor_hints; + int dx; + int dy; + + gboolean is_sealed; +}; + +G_DEFINE_BOXED_TYPE (GdkPopupLayout, gdk_popup_layout, + gdk_popup_layout_ref, + gdk_popup_layout_unref) + +/** + * gdk_popup_layout_new: (constructor) + * @anchor_rect: (not nullable): the anchor #GdkRectangle to align @surface with + * @rect_anchor: the point on @anchor_rect to align with @surface's anchor point + * @surface_anchor: the point on @surface to align with @rect's anchor point + * + * Create a popup layout description. Used together with + * gdk_surface_present_popup() to describe how a popup surface should be placed + * and behave on-screen. + * + * @anchor_rect is relative to the top-left corner of the surface's parent. + * @rect_anchor and @surface_anchor determine anchor points on @anchor_rect and + * surface to pin together. + * + * The position of @anchor_rect's anchor point can optionally be offset using + * gdk_popup_layout_set_offset(), which is equivalent to offsetting the + * position of surface. + * + * Returns: (transfer full): newly created instance of #GdkPopupLayout + */ +GdkPopupLayout * +gdk_popup_layout_new (const GdkRectangle *anchor_rect, + GdkGravity rect_anchor, + GdkGravity surface_anchor) +{ + GdkPopupLayout *layout; + + layout = g_new0 (GdkPopupLayout, 1); + g_ref_count_init (&layout->ref_count); + layout->anchor_rect = *anchor_rect; + layout->rect_anchor = rect_anchor; + layout->surface_anchor = surface_anchor; + + return layout; +} + +/** + * gdk_popup_layout_ref: + * @layout: a #GdkPopupLayout + * + * Increases the reference count of @value. + * + * Returns: the same @layout + */ +GdkPopupLayout * +gdk_popup_layout_ref (GdkPopupLayout *layout) +{ + g_ref_count_inc (&layout->ref_count); + return layout; +} + +/** + * gdk_popup_layout_unref: + * @layout: a #GdkPopupLayout + * + * Decreases the reference count of @value. + */ +void +gdk_popup_layout_unref (GdkPopupLayout *layout) +{ + if (g_ref_count_dec (&layout->ref_count)) + g_free (layout); +} + +/** + * gdk_popup_layout_copy: + * @layout: a #GdkPopupLayout + * + * Create a new #GdkPopupLayout and copy the contents of @layout into it. + * + * Returns: (transfer full): a copy of @layout. + */ +GdkPopupLayout * +gdk_popup_layout_copy (GdkPopupLayout *layout) +{ + GdkPopupLayout *new_layout; + + new_layout = g_new0 (GdkPopupLayout, 1); + g_ref_count_init (&new_layout->ref_count); + + new_layout->anchor_rect = layout->anchor_rect; + new_layout->rect_anchor = layout->rect_anchor; + new_layout->surface_anchor = layout->surface_anchor; + new_layout->anchor_hints = layout->anchor_hints; + new_layout->dx = layout->dx; + new_layout->dy = layout->dy; + + return new_layout; +} + +/** + * gdk_popup_layout_set_anchor_rect: + * @layout: a #GdkPopupLayout + * @anchor_rect: the new anchor rectangle + * + * Set the anchor rectangle. + */ +void +gdk_popup_layout_set_anchor_rect (GdkPopupLayout *layout, + const GdkRectangle *anchor_rect) +{ + layout->anchor_rect = *anchor_rect; +} + +/** + * gdk_popup_layout_get_anchor_rect: + * @layout: a #GdkPopupLayout + * + * Get the anchor rectangle. + * + * Returns: The anchor rectangle. + */ +const GdkRectangle * +gdk_popup_layout_get_anchor_rect (GdkPopupLayout *layout) +{ + return &layout->anchor_rect; +} + +/** + * gdk_popup_layout_set_rect_anchor: + * @layout: a #GdkPopupLayout + * @anchor: the new rect anchor + * + * Set the anchor on the anchor rectangle. + */ +void +gdk_popup_layout_set_rect_anchor (GdkPopupLayout *layout, + GdkGravity anchor) +{ + layout->rect_anchor = anchor; +} + +/** + * gdk_popup_layout_get_rect_anchor: + * @layout: a #GdkPopupLayout + * + * Returns: the anchor on the anchor rectangle. + */ +GdkGravity +gdk_popup_layout_get_rect_anchor (GdkPopupLayout *layout) +{ + return layout->rect_anchor; +} + +/** + * gdk_popup_layout_set_surface_anchor: + * @layout: a #GdkPopupLayout + * @anchor: the new popup surface anchor + * + * Set the anchor on the popup surface. + */ +void +gdk_popup_layout_set_surface_anchor (GdkPopupLayout *layout, + GdkGravity anchor) +{ + layout->surface_anchor = anchor; +} + +/** + * gdk_popup_layout_get_surface_anchor: + * @layout: a #GdkPopupLayout + * + * Returns: the anchor on the popup surface. + */ +GdkGravity +gdk_popup_layout_get_surface_anchor (GdkPopupLayout *layout) +{ + return layout->surface_anchor; +} + +/** + * gdk_popup_layout_set_anchor_hints: + * @layout: a #GdkPopupLayout + * @anchor_hints: the new #GdkAnchorHints + * + * Set new anchor hints. + * + * The set @anchor_hints determines how @surface will be moved if the anchor + * points cause it to move off-screen. For example, %GDK_ANCHOR_FLIP_X will + * replace %GDK_GRAVITY_NORTH_WEST with %GDK_GRAVITY_NORTH_EAST and vice versa + * if @surface extends beyond the left or right edges of the monitor. + */ +void +gdk_popup_layout_set_anchor_hints (GdkPopupLayout *layout, + GdkAnchorHints anchor_hints) +{ + layout->anchor_hints = anchor_hints; +} + +/** + * gdk_popup_layout_get_anchor_hints: + * @layout: a #GdkPopupLayout + * + * Get the #GdkAnchorHints. + * + * Returns: the #GdkAnchorHints. + */ +GdkAnchorHints +gdk_popup_layout_get_anchor_hints (GdkPopupLayout *layout) +{ + return layout->anchor_hints; +} + +/** + * gdk_popup_layout_set_offset: + * @layout: a #GdkPopupLayout + * @dx: x delta to offset the anchor rectangle with + * @dy: y delta to offset the anchor rectangle with + * + * Offset the position of the anchor rectangle with the given delta. + */ +void +gdk_popup_layout_set_offset (GdkPopupLayout *layout, + int dx, + int dy) +{ + layout->dx = dx; + layout->dy = dy; +} + +/** + * gdk_popup_layout_get_offset: + * @layout: a #GdkPopupLayout + * @dx: a pointer to where to store the delta x coordinate + * @dy: a pointer to where to store the delta y coordinate + * + * Get the delta the anchor rectangle is offset with + */ +void +gdk_popup_layout_get_offset (GdkPopupLayout *layout, + int *dx, + int *dy) +{ + if (dx) + *dx = layout->dx; + if (dy) + *dy = layout->dy; +} diff --git a/gdk/gdkpopuplayout.h b/gdk/gdkpopuplayout.h new file mode 100644 index 0000000000..9a07c0aeea --- /dev/null +++ b/gdk/gdkpopuplayout.h @@ -0,0 +1,136 @@ +/* GDK - The GIMP Drawing Kit + * Copyright (C) 2020 Red Hat + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +#ifndef __GDK_POPUP_LAYOUT_H__ +#define __GDK_POPUP_LAYOUT_H__ + +#if !defined (__GDK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * GdkAnchorHints: + * @GDK_ANCHOR_FLIP_X: allow flipping anchors horizontally + * @GDK_ANCHOR_FLIP_Y: allow flipping anchors vertically + * @GDK_ANCHOR_SLIDE_X: allow sliding surface horizontally + * @GDK_ANCHOR_SLIDE_Y: allow sliding surface vertically + * @GDK_ANCHOR_RESIZE_X: allow resizing surface horizontally + * @GDK_ANCHOR_RESIZE_Y: allow resizing surface vertically + * @GDK_ANCHOR_FLIP: allow flipping anchors on both axes + * @GDK_ANCHOR_SLIDE: allow sliding surface on both axes + * @GDK_ANCHOR_RESIZE: allow resizing surface on both axes + * + * Positioning hints for aligning a surface relative to a rectangle. + * + * These hints determine how the surface should be positioned in the case that + * the surface would fall off-screen if placed in its ideal position. + * + * For example, %GDK_ANCHOR_FLIP_X will replace %GDK_GRAVITY_NORTH_WEST with + * %GDK_GRAVITY_NORTH_EAST and vice versa if the surface extends beyond the left + * or right edges of the monitor. + * + * If %GDK_ANCHOR_SLIDE_X is set, the surface can be shifted horizontally to fit + * on-screen. If %GDK_ANCHOR_RESIZE_X is set, the surface can be shrunken + * horizontally to fit. + * + * In general, when multiple flags are set, flipping should take precedence over + * sliding, which should take precedence over resizing. + */ +typedef enum +{ + GDK_ANCHOR_FLIP_X = 1 << 0, + GDK_ANCHOR_FLIP_Y = 1 << 1, + GDK_ANCHOR_SLIDE_X = 1 << 2, + GDK_ANCHOR_SLIDE_Y = 1 << 3, + GDK_ANCHOR_RESIZE_X = 1 << 4, + GDK_ANCHOR_RESIZE_Y = 1 << 5, + GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y, + GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y, + GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y, +} GdkAnchorHints; + +/** + * GdkPopupLayout: + */ +typedef struct _GdkPopupLayout GdkPopupLayout; + +#define GDK_TYPE_POPUP_LAYOUT (gdk_popup_layout_get_type ()) + +GDK_AVAILABLE_IN_ALL +GType gdk_popup_layout_get_type (void); + +GDK_AVAILABLE_IN_ALL +GdkPopupLayout * gdk_popup_layout_new (const GdkRectangle *anchor_rect, + GdkGravity rect_anchor, + GdkGravity surface_anchor); + +GDK_AVAILABLE_IN_ALL +GdkPopupLayout * gdk_popup_layout_ref (GdkPopupLayout *layout); + +GDK_AVAILABLE_IN_ALL +void gdk_popup_layout_unref (GdkPopupLayout *layout); + +GDK_AVAILABLE_IN_ALL +GdkPopupLayout * gdk_popup_layout_copy (GdkPopupLayout *layout); + +GDK_AVAILABLE_IN_ALL +void gdk_popup_layout_set_anchor_rect (GdkPopupLayout *layout, + const GdkRectangle *anchor_rect); + +GDK_AVAILABLE_IN_ALL +const GdkRectangle * gdk_popup_layout_get_anchor_rect (GdkPopupLayout *layout); + +GDK_AVAILABLE_IN_ALL +void gdk_popup_layout_set_rect_anchor (GdkPopupLayout *layout, + GdkGravity anchor); + +GDK_AVAILABLE_IN_ALL +GdkGravity gdk_popup_layout_get_rect_anchor (GdkPopupLayout *layout); + +GDK_AVAILABLE_IN_ALL +void gdk_popup_layout_set_surface_anchor (GdkPopupLayout *layout, + GdkGravity anchor); + +GDK_AVAILABLE_IN_ALL +GdkGravity gdk_popup_layout_get_surface_anchor (GdkPopupLayout *layout); + +GDK_AVAILABLE_IN_ALL +void gdk_popup_layout_set_anchor_hints (GdkPopupLayout *layout, + GdkAnchorHints anchor_hints); + +GDK_AVAILABLE_IN_ALL +GdkAnchorHints gdk_popup_layout_get_anchor_hints (GdkPopupLayout *layout); + +GDK_AVAILABLE_IN_ALL +void gdk_popup_layout_set_offset (GdkPopupLayout *layout, + int dx, + int dy); + +GDK_AVAILABLE_IN_ALL +void gdk_popup_layout_get_offset (GdkPopupLayout *layout, + int *dx, + int *dy); + +G_END_DECLS + +#endif /* __GDK_POPUP_LAYOUT_H__ */ diff --git a/gdk/gdksurface.c b/gdk/gdksurface.c index 9790be98a3..dbda1833bc 100644 --- a/gdk/gdksurface.c +++ b/gdk/gdksurface.c @@ -69,7 +69,7 @@ */ enum { - MOVED_TO_RECT, + POPUP_LAYOUT_CHANGED, SIZE_CHANGED, RENDER, EVENT, @@ -247,36 +247,30 @@ maybe_flip_position (gint bounds_pos, } void -gdk_surface_move_to_rect_helper (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy, - GdkSurfaceMovedToRect moved_to_rect) -{ - GdkSurface *toplevel; +gdk_surface_layout_popup_helper (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout, + GdkRectangle *out_final_rect) +{ GdkDisplay *display; GdkMonitor *monitor; GdkRectangle bounds; - GdkRectangle root_rect = *rect; - GdkRectangle flipped_rect; + GdkRectangle root_rect; + GdkGravity rect_anchor; + GdkGravity surface_anchor; + int rect_anchor_dx; + int rect_anchor_dy; + GdkAnchorHints anchor_hints; GdkRectangle final_rect; gboolean flipped_x; gboolean flipped_y; int x, y; - /* This implementation only works for backends that - * can provide root coordinates via get_root_coords. - * Other backends need to implement move_to_rect. - */ - if (surface->surface_type == GDK_SURFACE_POPUP) - toplevel = surface->parent; - else - toplevel = surface->transient_for; + g_return_if_fail (surface->surface_type == GDK_SURFACE_POPUP); - gdk_surface_get_root_coords (toplevel, + root_rect = *gdk_popup_layout_get_anchor_rect (layout); + gdk_surface_get_root_coords (surface->parent, root_rect.x, root_rect.y, &root_rect.x, @@ -286,30 +280,33 @@ gdk_surface_move_to_rect_helper (GdkSurface *surface, monitor = get_monitor_for_rect (display, &root_rect); gdk_monitor_get_workarea (monitor, &bounds); - flipped_rect.width = surface->width - surface->shadow_left - surface->shadow_right; - flipped_rect.height = surface->height - surface->shadow_top - surface->shadow_bottom; - flipped_rect.x = maybe_flip_position (bounds.x, - bounds.width, - root_rect.x, - root_rect.width, - flipped_rect.width, - get_anchor_x_sign (rect_anchor), - get_anchor_x_sign (surface_anchor), - rect_anchor_dx, - anchor_hints & GDK_ANCHOR_FLIP_X, - &flipped_x); - flipped_rect.y = maybe_flip_position (bounds.y, - bounds.height, - root_rect.y, - root_rect.height, - flipped_rect.height, - get_anchor_y_sign (rect_anchor), - get_anchor_y_sign (surface_anchor), - rect_anchor_dy, - anchor_hints & GDK_ANCHOR_FLIP_Y, - &flipped_y); - - final_rect = flipped_rect; + rect_anchor = gdk_popup_layout_get_rect_anchor (layout); + surface_anchor = gdk_popup_layout_get_surface_anchor (layout); + gdk_popup_layout_get_offset (layout, &rect_anchor_dx, &rect_anchor_dy); + anchor_hints = gdk_popup_layout_get_anchor_hints (layout); + + final_rect.width = width - surface->shadow_left - surface->shadow_right; + final_rect.height = height - surface->shadow_top - surface->shadow_bottom; + final_rect.x = maybe_flip_position (bounds.x, + bounds.width, + root_rect.x, + root_rect.width, + final_rect.width, + get_anchor_x_sign (rect_anchor), + get_anchor_x_sign (surface_anchor), + rect_anchor_dx, + anchor_hints & GDK_ANCHOR_FLIP_X, + &flipped_x); + final_rect.y = maybe_flip_position (bounds.y, + bounds.height, + root_rect.y, + root_rect.height, + final_rect.height, + get_anchor_y_sign (rect_anchor), + get_anchor_y_sign (surface_anchor), + rect_anchor_dy, + anchor_hints & GDK_ANCHOR_FLIP_Y, + &flipped_y); if (anchor_hints & GDK_ANCHOR_SLIDE_X) { @@ -353,30 +350,30 @@ gdk_surface_move_to_rect_helper (GdkSurface *surface, final_rect.height = bounds.y + bounds.height - final_rect.y; } - flipped_rect.x -= surface->shadow_left; - flipped_rect.y -= surface->shadow_top; - flipped_rect.width += surface->shadow_left + surface->shadow_right; - flipped_rect.height += surface->shadow_top + surface->shadow_bottom; - final_rect.x -= surface->shadow_left; final_rect.y -= surface->shadow_top; final_rect.width += surface->shadow_left + surface->shadow_right; final_rect.height += surface->shadow_top + surface->shadow_bottom; - gdk_surface_get_origin (toplevel, &x, &y); + gdk_surface_get_origin (surface->parent, &x, &y); final_rect.x -= x; final_rect.y -= y; - flipped_rect.x -= x; - flipped_rect.y -= y; - moved_to_rect (surface, final_rect); + if (flipped_x) + { + rect_anchor = gdk_gravity_flip_horizontally (rect_anchor); + surface_anchor = gdk_gravity_flip_horizontally (surface_anchor); + } + if (flipped_y) + { + rect_anchor = gdk_gravity_flip_vertically (rect_anchor); + surface_anchor = gdk_gravity_flip_vertically (surface_anchor); + } + + surface->popup.rect_anchor = rect_anchor; + surface->popup.surface_anchor = surface_anchor; - g_signal_emit_by_name (surface, - "moved-to-rect", - &flipped_rect, - &final_rect, - flipped_x, - flipped_y); + *out_final_rect = final_rect; } static void @@ -481,42 +478,24 @@ gdk_surface_class_init (GdkSurfaceClass *klass) g_object_class_install_properties (object_class, LAST_PROP, properties); /** - * GdkSurface::moved-to-rect: - * @surface: the #GdkSurface that moved - * @flipped_rect: (nullable): the position of @surface after any possible - * flipping or %NULL if the backend can't obtain it - * @final_rect: (nullable): the final position of @surface or %NULL if the - * backend can't obtain it - * @flipped_x: %TRUE if the anchors were flipped horizontally - * @flipped_y: %TRUE if the anchors were flipped vertically - * - * Emitted when the position of @surface is finalized after being moved to a - * destination rectangle. + * GdkSurface::popup-layout-changed + * @surface: the #GdkSurface that was laid out * - * @surface might be flipped over the destination rectangle in order to keep - * it on-screen, in which case @flipped_x and @flipped_y will be set to %TRUE - * accordingly. + * Emitted when the layout of a popup @surface has changed, e.g. if the popup + * layout was reactive and after the parent moved causing the popover to end + * up partially off-screen. * - * @flipped_rect is the ideal position of @surface after any possible - * flipping, but before any possible sliding. @final_rect is @flipped_rect, - * but possibly translated in the case that flipping is still ineffective in - * keeping @surface on-screen. - * Stability: Private */ - signals[MOVED_TO_RECT] = - g_signal_new (g_intern_static_string ("moved-to-rect"), + signals[POPUP_LAYOUT_CHANGED] = + g_signal_new (g_intern_static_string ("popup-layout-changed"), G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - _gdk_marshal_VOID__POINTER_POINTER_BOOLEAN_BOOLEAN, + NULL, G_TYPE_NONE, - 4, - G_TYPE_POINTER, - G_TYPE_POINTER, - G_TYPE_BOOLEAN, - G_TYPE_BOOLEAN); + 0); /** * GdkSurface::size-changed: @@ -805,9 +784,8 @@ gdk_surface_new_temp (GdkDisplay *display, * * Create a new popup surface. * - * The surface will be attached to @parent and can - * be positioned relative to it using - * gdk_surface_move_to_rect(). + * The surface will be attached to @parent and can be positioned relative to it + * using gdk_surface_show_popup() or later using gdk_surface_layout_popup(). * * Returns: (transfer full): a new #GdkSurface */ @@ -1985,14 +1963,6 @@ gdk_surface_restack (GdkSurface *surface, GDK_SURFACE_GET_CLASS (surface)->restack_toplevel (surface, sibling, above); } -static void -grab_prepare_func (GdkSeat *seat, - GdkSurface *surface, - gpointer data) -{ - gdk_surface_show_internal (surface, TRUE); -} - /** * gdk_surface_show: * @surface: a #GdkSurface @@ -2004,25 +1974,18 @@ grab_prepare_func (GdkSeat *seat, * This function maps a surface so it’s visible onscreen. Its opposite * is gdk_surface_hide(). * + * This function may not be used on a #GdkSurface with the surface type + * GTK_SURFACE_POPUP. + * * When implementing a #GtkWidget, you should call this function on the widget's * #GdkSurface as part of the “map” method. */ void gdk_surface_show (GdkSurface *surface) { - if (surface->autohide) - { - gdk_seat_grab (gdk_display_get_default_seat (surface->display), - surface, - GDK_SEAT_CAPABILITY_ALL, - TRUE, - NULL, NULL, - grab_prepare_func, NULL); - } - else - { - gdk_surface_show_internal (surface, TRUE); - } + g_return_if_fail (surface->surface_type != GDK_SURFACE_POPUP); + + gdk_surface_show_internal (surface, TRUE); } /** @@ -2084,6 +2047,8 @@ G_GNUC_END_IGNORE_DEPRECATIONS GDK_SURFACE_GET_CLASS (surface)->hide (surface); + surface->popup.rect_anchor = 0; + surface->popup.surface_anchor = 0; surface->x = 0; surface->y = 0; } @@ -2109,52 +2074,70 @@ gdk_surface_resize (GdkSurface *surface, } /** - * gdk_surface_move_to_rect: - * @surface: the #GdkSurface to move - * @rect: (not nullable): the destination #GdkRectangle to align @surface with - * @rect_anchor: the point on @rect to align with @surface's anchor point - * @surface_anchor: the point on @surface to align with @rect's anchor point - * @anchor_hints: positioning hints to use when limited on space - * @rect_anchor_dx: horizontal offset to shift @surface, i.e. @rect's anchor - * point - * @rect_anchor_dy: vertical offset to shift @surface, i.e. @rect's anchor point - * - * Moves @surface to @rect, aligning their anchor points. - * - * @rect is relative to the top-left corner of the surface that @surface is - * transient for. @rect_anchor and @surface_anchor determine anchor points on - * @rect and @surface to pin together. @rect's anchor point can optionally be - * offset by @rect_anchor_dx and @rect_anchor_dy, which is equivalent to - * offsetting the position of @surface. - * - * @anchor_hints determines how @surface will be moved if the anchor points cause - * it to move off-screen. For example, %GDK_ANCHOR_FLIP_X will replace - * %GDK_GRAVITY_NORTH_WEST with %GDK_GRAVITY_NORTH_EAST and vice versa if - * @surface extends beyond the left or right edges of the monitor. - * - * Connect to the #GdkSurface::moved-to-rect signal to find out how it was - * actually positioned. + * gdk_surface_present_popup: + * @surface: the popup #GdkSurface to show + * @width: the unconstrained popup width to layout + * @height: the unconstrained popup height to layout + * @layout: the #GdkPopupLayout object used to layout + * + * Present @surface after having processed the #GdkPopupLayout rules. If the + * popup was previously now showing, it will be showed, otherwise it will + * change position according to @layout. + * + * After calling this function, the result of the layout can be queried + * using gdk_surface_get_position(), gdk_surface_get_width(), + * gdk_surface_get_height(), gdk_surface_get_popup_rect_anchor() and + * gdk_surface_get_popup_surface_anchor(). + * + * Presenting may have fail, for example if it was immediately hidden if the + * @surface was set to autohide. + * + * Returns: %FALSE if it failed to be presented, otherwise %TRUE. */ -void -gdk_surface_move_to_rect (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy) +gboolean +gdk_surface_present_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { - g_return_if_fail (GDK_IS_SURFACE (surface)); - g_return_if_fail (surface->parent || surface->transient_for); - g_return_if_fail (rect); - - GDK_SURFACE_GET_CLASS (surface)->move_to_rect (surface, - rect, - rect_anchor, - surface_anchor, - anchor_hints, - rect_anchor_dx, - rect_anchor_dy); + g_return_val_if_fail (GDK_IS_SURFACE (surface), FALSE); + g_return_val_if_fail (surface->parent, FALSE); + g_return_val_if_fail (layout, FALSE); + g_return_val_if_fail (!GDK_SURFACE_DESTROYED (surface), FALSE); + g_return_val_if_fail (width > 0 && height > 0, FALSE); + + return GDK_SURFACE_GET_CLASS (surface)->present_popup (surface, + width, + height, + layout); +} + +/** + * gdk_surface_get_popup_surface_anchor: + * @surface: a #GdkSurface + * + * Get the current popup surface anchor. The value returned may chage after + * calling gdk_surface_show_popup(), gdk_surface_layout_popup() or after the + * "popup-layout-changed" is emitted. + */ +GdkGravity +gdk_surface_get_popup_surface_anchor (GdkSurface *surface) +{ + return surface->popup.surface_anchor; +} + +/** + * gdk_surface_get_popup_rect_anchor: + * @surface: a #GdkSurface + * + * Get the current popup anchor rectangle anchor. The value + * returned may chage after calling gdk_surface_show_popup(), + * gdk_surface_layout_popup() or after the "popup-layout-changed" is emitted. + */ +GdkGravity +gdk_surface_get_popup_rect_anchor (GdkSurface *surface) +{ + return surface->popup.rect_anchor; } static void diff --git a/gdk/gdksurface.h b/gdk/gdksurface.h index 2d8c3d0b5f..6f50e66b7b 100644 --- a/gdk/gdksurface.h +++ b/gdk/gdksurface.h @@ -34,6 +34,7 @@ #include #include #include +#include G_BEGIN_DECLS @@ -143,47 +144,6 @@ typedef enum GDK_FUNC_CLOSE = 1 << 5 } GdkWMFunction; -/** - * GdkAnchorHints: - * @GDK_ANCHOR_FLIP_X: allow flipping anchors horizontally - * @GDK_ANCHOR_FLIP_Y: allow flipping anchors vertically - * @GDK_ANCHOR_SLIDE_X: allow sliding surface horizontally - * @GDK_ANCHOR_SLIDE_Y: allow sliding surface vertically - * @GDK_ANCHOR_RESIZE_X: allow resizing surface horizontally - * @GDK_ANCHOR_RESIZE_Y: allow resizing surface vertically - * @GDK_ANCHOR_FLIP: allow flipping anchors on both axes - * @GDK_ANCHOR_SLIDE: allow sliding surface on both axes - * @GDK_ANCHOR_RESIZE: allow resizing surface on both axes - * - * Positioning hints for aligning a surface relative to a rectangle. - * - * These hints determine how the surface should be positioned in the case that - * the surface would fall off-screen if placed in its ideal position. - * - * For example, %GDK_ANCHOR_FLIP_X will replace %GDK_GRAVITY_NORTH_WEST with - * %GDK_GRAVITY_NORTH_EAST and vice versa if the surface extends beyond the left - * or right edges of the monitor. - * - * If %GDK_ANCHOR_SLIDE_X is set, the surface can be shifted horizontally to fit - * on-screen. If %GDK_ANCHOR_RESIZE_X is set, the surface can be shrunken - * horizontally to fit. - * - * In general, when multiple flags are set, flipping should take precedence over - * sliding, which should take precedence over resizing. - */ -typedef enum -{ - GDK_ANCHOR_FLIP_X = 1 << 0, - GDK_ANCHOR_FLIP_Y = 1 << 1, - GDK_ANCHOR_SLIDE_X = 1 << 2, - GDK_ANCHOR_SLIDE_Y = 1 << 3, - GDK_ANCHOR_RESIZE_X = 1 << 4, - GDK_ANCHOR_RESIZE_Y = 1 << 5, - GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y, - GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y, - GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y -} GdkAnchorHints; - /** * GdkSurfaceEdge: * @GDK_SURFACE_EDGE_NORTH_WEST: the top left corner. @@ -407,13 +367,14 @@ void gdk_surface_resize (GdkSurface *surface, gint width, gint height); GDK_AVAILABLE_IN_ALL -void gdk_surface_move_to_rect (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy); +gboolean gdk_surface_present_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout); +GDK_AVAILABLE_IN_ALL +GdkGravity gdk_surface_get_popup_surface_anchor (GdkSurface *surface); +GDK_AVAILABLE_IN_ALL +GdkGravity gdk_surface_get_popup_rect_anchor (GdkSurface *surface); GDK_AVAILABLE_IN_ALL void gdk_surface_raise (GdkSurface *surface); diff --git a/gdk/gdksurfaceprivate.h b/gdk/gdksurfaceprivate.h index eafdb78c19..fa9674451d 100644 --- a/gdk/gdksurfaceprivate.h +++ b/gdk/gdksurfaceprivate.h @@ -73,6 +73,11 @@ struct _GdkSurface guint frame_clock_events_paused : 1; guint autohide : 1; + struct { + GdkGravity surface_anchor; + GdkGravity rect_anchor; + } popup; + guint update_and_descendants_freeze_count; gint width, height; @@ -116,13 +121,10 @@ struct _GdkSurfaceClass void (* toplevel_resize) (GdkSurface *surface, gint width, gint height); - void (* move_to_rect) (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy); + gboolean (* present_popup) (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout); void (* get_geometry) (GdkSurface *surface, gint *x, @@ -255,18 +257,71 @@ struct _GdkSurfaceClass void gdk_surface_set_state (GdkSurface *surface, GdkSurfaceState new_state); -typedef void (* GdkSurfaceMovedToRect) (GdkSurface *surface, - GdkRectangle final_rect); - -void -gdk_surface_move_to_rect_helper (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy, - GdkSurfaceMovedToRect moved_to_rect); +void gdk_surface_layout_popup_helper (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout, + GdkRectangle *out_final_rect); + +static inline GdkGravity +gdk_gravity_flip_horizontally (GdkGravity anchor) +{ + switch (anchor) + { + default: + case GDK_GRAVITY_STATIC: + case GDK_GRAVITY_NORTH_WEST: + return GDK_GRAVITY_NORTH_EAST; + case GDK_GRAVITY_NORTH: + return GDK_GRAVITY_NORTH; + case GDK_GRAVITY_NORTH_EAST: + return GDK_GRAVITY_NORTH_WEST; + case GDK_GRAVITY_WEST: + return GDK_GRAVITY_EAST; + case GDK_GRAVITY_CENTER: + return GDK_GRAVITY_CENTER; + case GDK_GRAVITY_EAST: + return GDK_GRAVITY_WEST; + case GDK_GRAVITY_SOUTH_WEST: + return GDK_GRAVITY_SOUTH_EAST; + case GDK_GRAVITY_SOUTH: + return GDK_GRAVITY_SOUTH; + case GDK_GRAVITY_SOUTH_EAST: + return GDK_GRAVITY_SOUTH_WEST; + } + + g_assert_not_reached (); +} + +static inline GdkGravity +gdk_gravity_flip_vertically (GdkGravity anchor) +{ + switch (anchor) + { + default: + case GDK_GRAVITY_STATIC: + case GDK_GRAVITY_NORTH_WEST: + return GDK_GRAVITY_SOUTH_WEST; + case GDK_GRAVITY_NORTH: + return GDK_GRAVITY_SOUTH; + case GDK_GRAVITY_NORTH_EAST: + return GDK_GRAVITY_SOUTH_EAST; + case GDK_GRAVITY_WEST: + return GDK_GRAVITY_WEST; + case GDK_GRAVITY_CENTER: + return GDK_GRAVITY_CENTER; + case GDK_GRAVITY_EAST: + return GDK_GRAVITY_EAST; + case GDK_GRAVITY_SOUTH_WEST: + return GDK_GRAVITY_NORTH_WEST; + case GDK_GRAVITY_SOUTH: + return GDK_GRAVITY_NORTH; + case GDK_GRAVITY_SOUTH_EAST: + return GDK_GRAVITY_NORTH_EAST; + } + + g_assert_not_reached (); +} G_END_DECLS diff --git a/gdk/meson.build b/gdk/meson.build index ed41b126ea..af04354d88 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -45,6 +45,7 @@ gdk_public_sources = files([ 'gdktexture.c', 'gdkvulkancontext.c', 'gdksurface.c', + 'gdkpopuplayout.c', 'gdkprofiler.c' ]) @@ -90,6 +91,7 @@ gdk_public_headers = files([ 'gdktypes.h', 'gdkvulkancontext.h', 'gdksurface.h', + 'gdkpopuplayout.h', ]) install_headers(gdk_public_headers, subdir: 'gtk-4.0/gdk/') diff --git a/gdk/quartz/gdksurface-quartz.c b/gdk/quartz/gdksurface-quartz.c index cd8599e281..ae259e7d42 100644 --- a/gdk/quartz/gdksurface-quartz.c +++ b/gdk/quartz/gdksurface-quartz.c @@ -1245,51 +1245,83 @@ gdk_surface_quartz_toplevel_resize (GdkSurface *surface, } static void -gdk_quartz_surface_moved_to_rect (GdkSurface *surface, - GdkRectangle final_rect) +gdk_quartz_surface_layout_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { - GdkSurface *toplevel; + GdkRectangle final_rect; int x, y; - if (surface->surface_type == GDK_SURFACE_POPUP) - toplevel = surface->parent; - else - toplevel = surface->transient_for; + gdk_surface_layout_popup_helper (surface, + width, + height, + layout, + &final_rect); - gdk_surface_get_origin (toplevel, &x, &y); + gdk_surface_get_origin (surface->parent, &x, &y); x += final_rect.x; y += final_rect.y; if (final_rect.width != surface->width || final_rect.height != surface->height) { - window_quartz_move_resize (surface, - x, y, - final_rect.width, final_rect.height); + move_resize_window_internal (surface, + x, + y, + final_rect.width, + final_rect.height); } else { - window_quartz_resize (surface, final_rect.width, final_rect.height); + window_quartz_move (surface, x, y); } } static void -gdk_quartz_surface_move_to_rect (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy) -{ - gdk_surface_move_to_rect_helper (surface, - rect, - rect_anchor, - surface_anchor, - anchor_hints, - rect_anchor_dx, - rect_anchor_dy, - gdk_quartz_surface_moved_to_rect); +show_popup (GdkSurface *surface) +{ + gdk_surface_raise (surface); + gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0); + _gdk_surface_update_viewable (surface); + gdk_quartz_surface_show (surface, FALSE); + gdk_surface_invalidate_rect (surface, NULL); +} + +static void +show_grabbing_popup (GdkSeat *seat, + GdkSurface *surface, + gpointer user_data) +{ + show_popup (surface); +} + +static gboolean +gdk_quartz_surface_present_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) +{ + gdk_quartz_surface_layout_popup (surface, width, height, layout); + + if (GDK_SURFACE_IS_MAPPED (surface)) + return TRUE; + + if (surface->autohide) + { + gdk_seat_grab (gdk_display_get_default_seat (surface->display), + surface, + GDK_SEAT_CAPABILITY_ALL, + TRUE, + NULL, NULL, + show_grabbing_popup, NULL); + } + else + { + show_popup (surface); + } + + return GDK_SURFACE_IS_MAPPED (surface); } /* Get the toplevel ordering from NSApp and update our own list. We do @@ -2675,7 +2707,7 @@ gdk_surface_impl_quartz_class_init (GdkSurfaceImplQuartzClass *klass) impl_class->lower = gdk_surface_quartz_lower; impl_class->restack_toplevel = gdk_surface_quartz_restack_toplevel; impl_class->toplevel_resize = gdk_surface_quartz_toplevel_resize; - impl_class->move_to_rect = gdk_surface_quartz_move_to_rect; + impl_class->present_popup = gdk_quartz_surface_present_popup; impl_class->get_geometry = gdk_surface_quartz_get_geometry; impl_class->get_root_coords = gdk_surface_quartz_get_root_coords; impl_class->get_device_state = gdk_surface_quartz_get_device_state; diff --git a/gdk/wayland/gdkdevice-wayland.c b/gdk/wayland/gdkdevice-wayland.c index 8ef0a3ae03..814c180386 100644 --- a/gdk/wayland/gdkdevice-wayland.c +++ b/gdk/wayland/gdkdevice-wayland.c @@ -4593,8 +4593,6 @@ gdk_wayland_seat_grab (GdkSeat *seat, if (!gdk_surface_is_visible (surface)) { gdk_wayland_seat_set_grab_surface (wayland_seat, NULL); - g_critical ("Surface %p has not been made visible in GdkSeatGrabPrepareFunc", - surface); return GDK_GRAB_NOT_VIEWABLE; } diff --git a/gdk/wayland/gdksurface-wayland.c b/gdk/wayland/gdksurface-wayland.c index d9302c75e8..5a1b767089 100644 --- a/gdk/wayland/gdksurface-wayland.c +++ b/gdk/wayland/gdksurface-wayland.c @@ -52,6 +52,13 @@ static guint signals[LAST_SIGNAL]; #define MAX_WL_BUFFER_SIZE (4083) /* 4096 minus header, string argument length and NUL byte */ +typedef enum _PopupState +{ + POPUP_STATE_IDLE, + POPUP_STATE_WAITING_FOR_CONFIGURE, + POPUP_STATE_WAITING_FOR_FRAME, +} PopupState; + struct _GdkWaylandSurface { GdkSurface parent_instance; @@ -83,6 +90,8 @@ struct _GdkWaylandSurface EGLSurface egl_surface; EGLSurface dummy_egl_surface; + PopupState popup_state; + unsigned int initial_configure_received : 1; unsigned int mapped : 1; unsigned int pending_commit : 1; @@ -91,7 +100,6 @@ struct _GdkWaylandSurface GdkSurfaceTypeHint hint; GdkSurface *transient_for; GdkSurface *popup_parent; - gboolean has_layout_data; int pending_buffer_offset_x; int pending_buffer_offset_y; @@ -137,13 +145,10 @@ struct _GdkWaylandSurface gulong parent_surface_committed_handler; struct { - GdkRectangle rect; - GdkGravity rect_anchor; - GdkGravity surface_anchor; - GdkAnchorHints anchor_hints; - gint rect_anchor_dx; - gint rect_anchor_dy; - } pending_move_to_rect; + GdkPopupLayout *layout; + int unconstrained_width; + int unconstrained_height; + } popup; struct { struct { @@ -160,8 +165,11 @@ struct _GdkWaylandSurface } popup; uint32_t serial; + gboolean is_dirty; } pending; + int state_freeze_count; + struct { GdkWaylandSurfaceExported callback; gpointer user_data; @@ -199,15 +207,12 @@ static void gdk_wayland_surface_move_resize (GdkSurface *surface, gint width, gint height); -static void calculate_moved_to_rect_result (GdkSurface *surface, - int x, - int y, - int width, - int height, - GdkRectangle *flipped_rect, - GdkRectangle *final_rect, - gboolean *flipped_x, - gboolean *flipped_y); +static void update_popup_layout_state (GdkSurface *surface, + int x, + int y, + int width, + int height, + GdkPopupLayout *layout); static gboolean gdk_wayland_surface_is_exported (GdkSurface *surface); @@ -223,6 +228,32 @@ gdk_wayland_surface_init (GdkWaylandSurface *impl) impl->shortcuts_inhibitors = g_hash_table_new (NULL, NULL); } +static void +gdk_wayland_surface_freeze_state (GdkSurface *surface) +{ + GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + + impl->state_freeze_count++; +} + +static void +gdk_wayland_surface_thaw_state (GdkSurface *surface) +{ + GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + + g_assert (impl->state_freeze_count > 0); + + impl->state_freeze_count--; + + if (impl->state_freeze_count > 0) + return; + + if (impl->pending.is_dirty) + gdk_wayland_surface_configure (surface); + + g_assert (!impl->display_server.xdg_popup); +} + static void _gdk_wayland_screen_add_orphan_dialog (GdkSurface *surface) { @@ -341,6 +372,44 @@ fill_presentation_time_from_frame_time (GdkFrameTimings *timings, } } +static GdkSurface * +get_popup_toplevel (GdkSurface *surface) +{ + if (surface->parent) + return get_popup_toplevel (surface->parent); + else + return surface; +} + +static void +freeze_popup_toplevel_state (GdkSurface *surface) +{ + GdkSurface *toplevel; + + toplevel = get_popup_toplevel (surface); + gdk_wayland_surface_freeze_state (toplevel); +} + +static void +thaw_popup_toplevel_state (GdkSurface *surface) +{ + GdkSurface *toplevel; + + toplevel = get_popup_toplevel (surface); + gdk_wayland_surface_thaw_state (toplevel); +} + +static void +finish_pending_relayout (GdkSurface *surface) +{ + GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + + g_assert (impl->popup_state == POPUP_STATE_WAITING_FOR_FRAME); + impl->popup_state = POPUP_STATE_IDLE; + + thaw_popup_toplevel_state (surface); +} + static void frame_callback (void *data, struct wl_callback *callback, @@ -364,6 +433,18 @@ frame_callback (void *data, if (!impl->awaiting_frame) return; + switch (impl->popup_state) + { + case POPUP_STATE_IDLE: + case POPUP_STATE_WAITING_FOR_CONFIGURE: + break; + case POPUP_STATE_WAITING_FOR_FRAME: + finish_pending_relayout (surface); + break; + default: + g_assert_not_reached (); + } + impl->awaiting_frame = FALSE; if (impl->awaiting_frame_frozen) { @@ -464,11 +545,8 @@ on_frame_clock_after_paint (GdkFrameClock *clock, { GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); - if (impl->pending_commit) + if (impl->pending_commit && surface->update_freeze_count == 0) { - if (surface->update_freeze_count > 0) - return; - gdk_wayland_surface_request_frame (surface); /* From this commit forward, we can't write to the buffer, @@ -671,6 +749,8 @@ gdk_wayland_surface_dispose (GObject *object) impl = GDK_WAYLAND_SURFACE (surface); + g_clear_object (&impl->popup_parent); + if (impl->event_queue) { GdkWaylandDisplay *display_wayland = @@ -1202,12 +1282,6 @@ gdk_wayland_surface_configure_popup (GdkSurface *surface) { GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); int x, y, width, height; - GdkRectangle flipped_rect; - GdkRectangle final_rect; - gboolean flipped_x; - gboolean flipped_y; - - g_return_if_fail (impl->transient_for); if (impl->display_server.xdg_popup) { @@ -1220,6 +1294,18 @@ gdk_wayland_surface_configure_popup (GdkSurface *surface) impl->pending.serial); } + switch (impl->popup_state) + { + case POPUP_STATE_WAITING_FOR_CONFIGURE: + impl->popup_state = POPUP_STATE_WAITING_FOR_FRAME; + break; + case POPUP_STATE_IDLE: + case POPUP_STATE_WAITING_FOR_FRAME: + break; + default: + g_assert_not_reached (); + } + x = impl->pending.popup.x; y = impl->pending.popup.y; width = impl->pending.popup.width; @@ -1227,20 +1313,12 @@ gdk_wayland_surface_configure_popup (GdkSurface *surface) gdk_wayland_surface_resize (surface, width, height, impl->scale); - calculate_moved_to_rect_result (surface, - x, y, - width, height, - &flipped_rect, - &final_rect, - &flipped_x, - &flipped_y); + update_popup_layout_state (surface, + x, y, + width, height, + impl->popup.layout); - g_signal_emit_by_name (surface, - "moved-to-rect", - &flipped_rect, - &final_rect, - flipped_x, - flipped_y); + gdk_surface_invalidate_rect (surface, NULL); } static void @@ -1260,6 +1338,8 @@ gdk_wayland_surface_configure (GdkSurface *surface) gdk_wayland_surface_configure_toplevel (surface); else g_warn_if_reached (); + + memset (&impl->pending, 0, sizeof (impl->pending)); } static void @@ -1268,8 +1348,12 @@ gdk_wayland_surface_handle_configure (GdkSurface *surface, { GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + impl->pending.is_dirty = TRUE; impl->pending.serial = serial; + if (impl->state_freeze_count > 0) + return; + gdk_wayland_surface_configure (surface); } @@ -1776,70 +1860,28 @@ gdk_wayland_surface_announce_csd (GdkSurface *surface) ORG_KDE_KWIN_SERVER_DECORATION_MANAGER_MODE_CLIENT); } -static GdkSurface * -get_real_parent_and_translate (GdkSurface *surface, - gint *x, - gint *y) -{ - GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); - GdkSurface *parent = impl->transient_for; - - return parent; -} - -static void -translate_to_real_parent_surface_geometry (GdkSurface *surface, - gint *x, - gint *y) -{ - GdkSurface *parent; - - parent = get_real_parent_and_translate (surface, x, y); - - *x -= parent->shadow_left; - *y -= parent->shadow_top; -} - -static GdkSurface * -translate_from_real_parent_surface_geometry (GdkSurface *surface, - gint *x, - gint *y) -{ - GdkSurface *parent; - gint dx = 0; - gint dy = 0; - - parent = get_real_parent_and_translate (surface, &dx, &dy); - - *x -= dx - parent->shadow_left; - *y -= dy - parent->shadow_top; - - return parent; -} - static void -calculate_popup_rect (GdkSurface *surface, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkRectangle *out_rect) +calculate_popup_rect (GdkSurface *surface, + GdkPopupLayout *layout, + GdkRectangle *out_rect) { GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); - GdkRectangle geometry; + int width, height; GdkRectangle anchor_rect; + int dx, dy; int x = 0, y = 0; - gdk_wayland_surface_get_window_geometry (surface, &geometry); + width = (impl->popup.unconstrained_width - + (impl->margin_left + impl->margin_right)); + height = (impl->popup.unconstrained_height - + (impl->margin_top + impl->margin_bottom)); - anchor_rect = (GdkRectangle) { - .x = (impl->pending_move_to_rect.rect.x + - impl->pending_move_to_rect.rect_anchor_dx), - .y = (impl->pending_move_to_rect.rect.y + - impl->pending_move_to_rect.rect_anchor_dy), - .width = impl->pending_move_to_rect.rect.width, - .height = impl->pending_move_to_rect.rect.height - }; + anchor_rect = *gdk_popup_layout_get_anchor_rect (layout); + gdk_popup_layout_get_offset (layout, &dx, &dy); + anchor_rect.x += dx; + anchor_rect.y += dy; - switch (rect_anchor) + switch (gdk_popup_layout_get_rect_anchor (layout)) { default: case GDK_GRAVITY_STATIC: @@ -1881,137 +1923,71 @@ calculate_popup_rect (GdkSurface *surface, break; } - switch (surface_anchor) + switch (gdk_popup_layout_get_surface_anchor (layout)) { default: case GDK_GRAVITY_STATIC: case GDK_GRAVITY_NORTH_WEST: break; case GDK_GRAVITY_NORTH: - x -= geometry.width / 2; + x -= width / 2; break; case GDK_GRAVITY_NORTH_EAST: - x -= geometry.width; + x -= width; break; case GDK_GRAVITY_WEST: - y -= geometry.height / 2; + y -= height / 2; break; case GDK_GRAVITY_CENTER: - x -= geometry.width / 2; - y -= geometry.height / 2; + x -= width / 2; + y -= height / 2; break; case GDK_GRAVITY_EAST: - x -= geometry.width; - y -= geometry.height / 2; + x -= width; + y -= height / 2; break; case GDK_GRAVITY_SOUTH_WEST: - y -= geometry.height; + y -= height; break; case GDK_GRAVITY_SOUTH: - x -= geometry.width / 2; - y -= geometry.height; + x -= width / 2; + y -= height; break; case GDK_GRAVITY_SOUTH_EAST: - x -= geometry.width; - y -= geometry.height; + x -= width; + y -= height; break; } *out_rect = (GdkRectangle) { .x = x, .y = y, - .width = geometry.width, - .height = geometry.height + .width = width, + .height = height }; } -static GdkGravity -flip_anchor_horizontally (GdkGravity anchor) -{ - switch (anchor) - { - default: - case GDK_GRAVITY_STATIC: - case GDK_GRAVITY_NORTH_WEST: - return GDK_GRAVITY_NORTH_EAST; - case GDK_GRAVITY_NORTH: - return GDK_GRAVITY_NORTH; - case GDK_GRAVITY_NORTH_EAST: - return GDK_GRAVITY_NORTH_WEST; - case GDK_GRAVITY_WEST: - return GDK_GRAVITY_EAST; - case GDK_GRAVITY_CENTER: - return GDK_GRAVITY_CENTER; - case GDK_GRAVITY_EAST: - return GDK_GRAVITY_WEST; - case GDK_GRAVITY_SOUTH_WEST: - return GDK_GRAVITY_SOUTH_EAST; - case GDK_GRAVITY_SOUTH: - return GDK_GRAVITY_SOUTH; - case GDK_GRAVITY_SOUTH_EAST: - return GDK_GRAVITY_SOUTH_WEST; - } - - g_assert_not_reached (); -} - -static GdkGravity -flip_anchor_vertically (GdkGravity anchor) -{ - switch (anchor) - { - default: - case GDK_GRAVITY_STATIC: - case GDK_GRAVITY_NORTH_WEST: - return GDK_GRAVITY_SOUTH_WEST; - case GDK_GRAVITY_NORTH: - return GDK_GRAVITY_SOUTH; - case GDK_GRAVITY_NORTH_EAST: - return GDK_GRAVITY_SOUTH_EAST; - case GDK_GRAVITY_WEST: - return GDK_GRAVITY_WEST; - case GDK_GRAVITY_CENTER: - return GDK_GRAVITY_CENTER; - case GDK_GRAVITY_EAST: - return GDK_GRAVITY_EAST; - case GDK_GRAVITY_SOUTH_WEST: - return GDK_GRAVITY_NORTH_WEST; - case GDK_GRAVITY_SOUTH: - return GDK_GRAVITY_NORTH; - case GDK_GRAVITY_SOUTH_EAST: - return GDK_GRAVITY_NORTH_EAST; - } - - g_assert_not_reached (); -} - static void -calculate_moved_to_rect_result (GdkSurface *surface, - int x, - int y, - int width, - int height, - GdkRectangle *flipped_rect, - GdkRectangle *final_rect, - gboolean *flipped_x, - gboolean *flipped_y) +update_popup_layout_state (GdkSurface *surface, + int x, + int y, + int width, + int height, + GdkPopupLayout *layout) { - GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); - GdkSurface *parent; gint surface_x, surface_y; gint surface_width, surface_height; GdkRectangle best_rect; + GdkRectangle flipped_rect; + GdkGravity rect_anchor; + GdkGravity surface_anchor; + GdkAnchorHints anchor_hints; - parent = translate_from_real_parent_surface_geometry (surface, &x, &y); - *final_rect = (GdkRectangle) { - .x = x, - .y = y, - .width = width, - .height = height, - }; + x += surface->parent->shadow_left; + y += surface->parent->shadow_top; - surface_x = parent->x + x; - surface_y = parent->y + y; + surface_x = x; + surface_y = y; surface_width = width + surface->shadow_left + surface->shadow_right; surface_height = height + surface->shadow_top + surface->shadow_bottom; @@ -2019,79 +1995,117 @@ calculate_moved_to_rect_result (GdkSurface *surface, surface_x, surface_y, surface_width, surface_height); - calculate_popup_rect (surface, - impl->pending_move_to_rect.rect_anchor, - impl->pending_move_to_rect.surface_anchor, - &best_rect); + rect_anchor = gdk_popup_layout_get_rect_anchor (layout); + surface_anchor = gdk_popup_layout_get_surface_anchor (layout); + anchor_hints = gdk_popup_layout_get_anchor_hints (layout); - *flipped_rect = best_rect; + calculate_popup_rect (surface, layout, &best_rect); + + flipped_rect = best_rect; if (x != best_rect.x && - impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_X) + anchor_hints & GDK_ANCHOR_FLIP_X) { GdkRectangle flipped_x_rect; GdkGravity flipped_rect_anchor; GdkGravity flipped_surface_anchor; - - flipped_rect_anchor = - flip_anchor_horizontally (impl->pending_move_to_rect.rect_anchor); - flipped_surface_anchor = - flip_anchor_horizontally (impl->pending_move_to_rect.surface_anchor), + GdkPopupLayout *flipped_layout; + + flipped_rect_anchor = gdk_gravity_flip_horizontally (rect_anchor); + flipped_surface_anchor = gdk_gravity_flip_horizontally (surface_anchor); + flipped_layout = gdk_popup_layout_copy (layout); + gdk_popup_layout_set_rect_anchor (flipped_layout, + flipped_rect_anchor); + gdk_popup_layout_set_surface_anchor (flipped_layout, + flipped_surface_anchor); calculate_popup_rect (surface, - flipped_rect_anchor, - flipped_surface_anchor, + flipped_layout, &flipped_x_rect); + gdk_popup_layout_unref (flipped_layout); if (flipped_x_rect.x == x) - flipped_rect->x = x; + flipped_rect.x = x; } if (y != best_rect.y && - impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_Y) + anchor_hints & GDK_ANCHOR_FLIP_Y) { GdkRectangle flipped_y_rect; GdkGravity flipped_rect_anchor; GdkGravity flipped_surface_anchor; - - flipped_rect_anchor = - flip_anchor_vertically (impl->pending_move_to_rect.rect_anchor); - flipped_surface_anchor = - flip_anchor_vertically (impl->pending_move_to_rect.surface_anchor), + GdkPopupLayout *flipped_layout; + + flipped_rect_anchor = gdk_gravity_flip_vertically (rect_anchor); + flipped_surface_anchor = gdk_gravity_flip_vertically (surface_anchor); + flipped_layout = gdk_popup_layout_copy (layout); + gdk_popup_layout_set_rect_anchor (flipped_layout, + flipped_rect_anchor); + gdk_popup_layout_set_surface_anchor (flipped_layout, + flipped_surface_anchor); calculate_popup_rect (surface, - flipped_rect_anchor, - flipped_surface_anchor, + flipped_layout, &flipped_y_rect); + gdk_popup_layout_unref (flipped_layout); if (flipped_y_rect.y == y) - flipped_rect->y = y; + flipped_rect.y = y; } - *flipped_x = flipped_rect->x != best_rect.x; - *flipped_y = flipped_rect->y != best_rect.y; + if (flipped_rect.x != best_rect.x) + { + rect_anchor = gdk_gravity_flip_horizontally (rect_anchor); + surface_anchor = gdk_gravity_flip_horizontally (surface_anchor); + } + if (flipped_rect.y != best_rect.y) + { + rect_anchor = gdk_gravity_flip_vertically (rect_anchor); + surface_anchor = gdk_gravity_flip_vertically (surface_anchor); + } + + surface->popup.rect_anchor = rect_anchor; + surface->popup.surface_anchor = surface_anchor; } static gpointer -create_dynamic_positioner (GdkSurface *surface) +create_dynamic_positioner (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + GdkSurface *parent = surface->parent; GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface)); GdkRectangle geometry; uint32_t constraint_adjustment = ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_NONE; + const GdkRectangle *anchor_rect; gint real_anchor_rect_x, real_anchor_rect_y; gint anchor_rect_width, anchor_rect_height; + int rect_anchor_dx; + int rect_anchor_dy; + GdkGravity rect_anchor; + GdkGravity surface_anchor; + GdkAnchorHints anchor_hints; - g_warn_if_fail (impl->has_layout_data); + geometry = (GdkRectangle) { + .x = impl->margin_left, + .y = impl->margin_top, + .width = width - (impl->margin_left + impl->margin_right), + .height = height - (impl->margin_top + impl->margin_bottom), + }; - gdk_wayland_surface_get_window_geometry (surface, &geometry); + anchor_rect = gdk_popup_layout_get_anchor_rect (layout); + real_anchor_rect_x = anchor_rect->x - parent->shadow_left; + real_anchor_rect_y = anchor_rect->y - parent->shadow_top; + + anchor_rect_width = anchor_rect->width; + anchor_rect_height = anchor_rect->height; - real_anchor_rect_x = impl->pending_move_to_rect.rect.x; - real_anchor_rect_y = impl->pending_move_to_rect.rect.y; - translate_to_real_parent_surface_geometry (surface, - &real_anchor_rect_x, - &real_anchor_rect_y); + gdk_popup_layout_get_offset (layout, &rect_anchor_dx, &rect_anchor_dy); - anchor_rect_width = impl->pending_move_to_rect.rect.width; - anchor_rect_height = impl->pending_move_to_rect.rect.height; + rect_anchor = gdk_popup_layout_get_rect_anchor (layout); + surface_anchor = gdk_popup_layout_get_surface_anchor (layout); + + anchor_hints = gdk_popup_layout_get_anchor_hints (layout); switch (display->shell_variant) { @@ -2109,27 +2123,25 @@ create_dynamic_positioner (GdkSurface *surface) real_anchor_rect_y, anchor_rect_width, anchor_rect_height); - xdg_positioner_set_offset (positioner, - impl->pending_move_to_rect.rect_anchor_dx, - impl->pending_move_to_rect.rect_anchor_dy); + xdg_positioner_set_offset (positioner, rect_anchor_dx, rect_anchor_dy); - anchor = rect_anchor_to_anchor (impl->pending_move_to_rect.rect_anchor); + anchor = rect_anchor_to_anchor (rect_anchor); xdg_positioner_set_anchor (positioner, anchor); - gravity = surface_anchor_to_gravity (impl->pending_move_to_rect.surface_anchor); + gravity = surface_anchor_to_gravity (surface_anchor); xdg_positioner_set_gravity (positioner, gravity); - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_X) + if (anchor_hints & GDK_ANCHOR_FLIP_X) constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_Y) + if (anchor_hints & GDK_ANCHOR_FLIP_Y) constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_SLIDE_X) + if (anchor_hints & GDK_ANCHOR_SLIDE_X) constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_SLIDE_Y) + if (anchor_hints & GDK_ANCHOR_SLIDE_Y) constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_RESIZE_X) + if (anchor_hints & GDK_ANCHOR_RESIZE_X) constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_RESIZE_Y) + if (anchor_hints & GDK_ANCHOR_RESIZE_Y) constraint_adjustment |= XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y; xdg_positioner_set_constraint_adjustment (positioner, constraint_adjustment); @@ -2151,26 +2163,26 @@ create_dynamic_positioner (GdkSurface *surface) anchor_rect_width, anchor_rect_height); zxdg_positioner_v6_set_offset (positioner, - impl->pending_move_to_rect.rect_anchor_dx, - impl->pending_move_to_rect.rect_anchor_dy); + rect_anchor_dx, + rect_anchor_dy); - anchor = rect_anchor_to_anchor_legacy (impl->pending_move_to_rect.rect_anchor); + anchor = rect_anchor_to_anchor_legacy (rect_anchor); zxdg_positioner_v6_set_anchor (positioner, anchor); - gravity = surface_anchor_to_gravity_legacy (impl->pending_move_to_rect.surface_anchor); + gravity = surface_anchor_to_gravity_legacy (surface_anchor); zxdg_positioner_v6_set_gravity (positioner, gravity); - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_X) + if (anchor_hints & GDK_ANCHOR_FLIP_X) constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_FLIP_Y) + if (anchor_hints & GDK_ANCHOR_FLIP_Y) constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_SLIDE_X) + if (anchor_hints & GDK_ANCHOR_SLIDE_X) constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_SLIDE_Y) + if (anchor_hints & GDK_ANCHOR_SLIDE_Y) constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_RESIZE_X) + if (anchor_hints & GDK_ANCHOR_RESIZE_X) constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_X; - if (impl->pending_move_to_rect.anchor_hints & GDK_ANCHOR_RESIZE_Y) + if (anchor_hints & GDK_ANCHOR_RESIZE_Y) constraint_adjustment |= ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y; zxdg_positioner_v6_set_constraint_adjustment (positioner, constraint_adjustment); @@ -2202,7 +2214,10 @@ can_map_grabbing_popup (GdkSurface *surface, static void gdk_wayland_surface_create_xdg_popup (GdkSurface *surface, GdkSurface *parent, - GdkWaylandSeat *grab_input_seat) + GdkWaylandSeat *grab_input_seat, + int width, + int height, + GdkPopupLayout *layout) { GdkWaylandDisplay *display = GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface)); GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); @@ -2235,7 +2250,7 @@ gdk_wayland_surface_create_xdg_popup (GdkSurface *surface, gdk_surface_freeze_updates (surface); - positioner = create_dynamic_positioner (surface); + positioner = create_dynamic_positioner (surface, width, height, layout); switch (display->shell_variant) { @@ -2302,7 +2317,14 @@ gdk_wayland_surface_create_xdg_popup (GdkSurface *surface, gdk_profiler_add_mark (g_get_monotonic_time (), 0, "wayland", "surface commit"); wl_surface_commit (impl->display_server.wl_surface); - impl->popup_parent = parent; + if (surface->surface_type == GDK_SURFACE_POPUP) + { + g_assert (impl->popup_state == POPUP_STATE_IDLE); + impl->popup_state = POPUP_STATE_WAITING_FOR_CONFIGURE; + freeze_popup_toplevel_state (surface); + } + + g_set_object (&impl->popup_parent, parent); display->current_popups = g_list_append (display->current_popups, surface); if (grab_input_seat) { @@ -2360,10 +2382,9 @@ should_map_as_popup (GdkSurface *surface) } static void -gdk_wayland_surface_map (GdkSurface *surface) +gdk_wayland_surface_map_toplevel (GdkSurface *surface) { GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); - GdkSurface *parent = NULL; if (!should_be_mapped (surface)) return; @@ -2371,25 +2392,7 @@ gdk_wayland_surface_map (GdkSurface *surface) if (impl->mapped) return; - if (should_map_as_popup (surface)) - { - GdkWaylandSeat *grab_input_seat; - - parent = surface->parent; - if (!parent) - { - g_warning ("Couldn't map as surface %p as popup because it doesn't have a parent", - surface); - return; - } - - grab_input_seat = find_grab_input_seat (surface, parent); - gdk_wayland_surface_create_xdg_popup (surface, parent, grab_input_seat); - } - else - { - gdk_wayland_surface_create_xdg_toplevel (surface); - } + gdk_wayland_surface_create_xdg_toplevel (surface); impl->mapped = TRUE; } @@ -2400,10 +2403,12 @@ gdk_wayland_surface_show (GdkSurface *surface, { GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + g_return_if_fail (!should_map_as_popup (surface)); + if (!impl->display_server.wl_surface) gdk_wayland_surface_create_surface (surface); - gdk_wayland_surface_map (surface); + gdk_wayland_surface_map_toplevel (surface); } static void @@ -2517,6 +2522,23 @@ gdk_wayland_surface_hide_surface (GdkSurface *surface) gdk_surface_thaw_updates (surface); } + if (surface->surface_type == GDK_SURFACE_POPUP) + { + switch (impl->popup_state) + { + case POPUP_STATE_WAITING_FOR_CONFIGURE: + case POPUP_STATE_WAITING_FOR_FRAME: + thaw_popup_toplevel_state (surface); + break; + case POPUP_STATE_IDLE: + break; + default: + g_assert_not_reached (); + } + + impl->popup_state = POPUP_STATE_IDLE; + } + if (impl->display_server.gtk_surface) { gtk_surface1_destroy (impl->display_server.gtk_surface); @@ -2533,6 +2555,8 @@ gdk_wayland_surface_hide_surface (GdkSurface *surface) if (impl->hint == GDK_SURFACE_TYPE_HINT_DIALOG && !impl->transient_for) display_wayland->orphan_dialogs = g_list_remove (display_wayland->orphan_dialogs, surface); + + g_clear_pointer (&impl->popup.layout, gdk_popup_layout_unref); } unset_transient_for_exported (surface); @@ -2606,41 +2630,217 @@ gdk_wayland_surface_toplevel_resize (GdkSurface *surface, impl->scale); } -/* Avoid zero width/height as this is a protocol error */ +static gboolean +is_fallback_relayout_possible (GdkSurface *surface) +{ + GList *l; + + for (l = surface->children; l; l = l->next) + { + GdkSurface *child = l->data; + + if (GDK_WAYLAND_SURFACE (child)->mapped) + return FALSE; + } + + return TRUE; +} + +static void +queue_relayout_fallback (GdkSurface *surface, + GdkPopupLayout *layout) +{ + GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + + if (!is_fallback_relayout_possible (surface)) + return; + + gdk_wayland_surface_hide_surface (surface); + gdk_surface_present_popup (surface, + impl->popup.unconstrained_width, + impl->popup.unconstrained_height, + layout); +} + +static void +do_queue_relayout (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) +{ + GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + + g_assert (is_realized_popup (surface)); + g_assert (impl->popup_state == POPUP_STATE_IDLE || + impl->popup_state == POPUP_STATE_WAITING_FOR_FRAME); + + g_clear_pointer (&impl->popup.layout, gdk_popup_layout_unref); + impl->popup.layout = gdk_popup_layout_copy (layout); + impl->popup.unconstrained_width = width; + impl->popup.unconstrained_height = height; + + queue_relayout_fallback (surface, layout); +} + +static gboolean +is_relayout_finished (GdkSurface *surface) +{ + GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + + if (!impl->initial_configure_received) + return FALSE; + + return TRUE; +} + static void -sanitize_anchor_rect (GdkSurface *surface, - GdkRectangle *rect) +gdk_wayland_surface_map_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { - gint original_width = rect->width; - gint original_height = rect->height; + GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + GdkSurface *parent; + GdkWaylandSeat *grab_input_seat; + + if (!should_be_mapped (surface)) + return; - rect->width = MAX (1, rect->width); - rect->height = MAX (1, rect->height); - rect->x = MAX (rect->x + original_width - rect->width, 0); - rect->y = MAX (rect->y + original_height - rect->height, 0); + if (impl->mapped) + return; + + parent = surface->parent; + if (!parent) + { + g_warning ("Couldn't map as surface %p as popup because it doesn't have a parent", + surface); + return; + } + + if (surface->autohide) + grab_input_seat = find_grab_input_seat (surface, parent); + else + grab_input_seat = NULL; + gdk_wayland_surface_create_xdg_popup (surface, + parent, + grab_input_seat, + width, height, + layout); + + impl->popup.layout = gdk_popup_layout_copy (layout); + impl->popup.unconstrained_width = width; + impl->popup.unconstrained_height = height; + impl->mapped = TRUE; } static void -gdk_wayland_surface_move_to_rect (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy) +show_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); - impl->pending_move_to_rect.rect = *rect; - sanitize_anchor_rect (surface, &impl->pending_move_to_rect.rect); + if (!impl->display_server.wl_surface) + gdk_wayland_surface_create_surface (surface); + + gdk_wayland_surface_map_popup (surface, width, height, layout); +} - impl->pending_move_to_rect.rect_anchor = rect_anchor; - impl->pending_move_to_rect.surface_anchor = surface_anchor; - impl->pending_move_to_rect.anchor_hints = anchor_hints; - impl->pending_move_to_rect.rect_anchor_dx = rect_anchor_dx; - impl->pending_move_to_rect.rect_anchor_dy = rect_anchor_dy; +typedef struct +{ + int width; + int height; + GdkPopupLayout *layout; +} GrabPrepareData; + +static void +show_grabbing_popup (GdkSeat *seat, + GdkSurface *surface, + gpointer user_data) +{ + GrabPrepareData *data = user_data; - impl->has_layout_data = TRUE; + show_popup (surface, data->width, data->height, data->layout); +} + +static void +reposition_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) +{ + GdkWaylandSurface *impl = GDK_WAYLAND_SURFACE (surface); + + switch (impl->popup_state) + { + case POPUP_STATE_IDLE: + case POPUP_STATE_WAITING_FOR_FRAME: + do_queue_relayout (surface, width, height, layout); + break; + case POPUP_STATE_WAITING_FOR_CONFIGURE: + g_warn_if_reached (); + break; + default: + g_assert_not_reached (); + } +} + +static gboolean +gdk_wayland_surface_present_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) +{ + GdkWaylandDisplay *display_wayland = + GDK_WAYLAND_DISPLAY (gdk_surface_get_display (surface)); + GdkWaylandSurface *impl; + + g_return_val_if_fail (should_map_as_popup (surface), FALSE); + + impl = GDK_WAYLAND_SURFACE (surface); + + if (!impl->mapped) + { + if (surface->autohide) + { + GrabPrepareData data; + + data = (GrabPrepareData) { + .width = width, + .height = height, + .layout = layout, + }; + gdk_seat_grab (gdk_display_get_default_seat (surface->display), + surface, + GDK_SEAT_CAPABILITY_ALL, + TRUE, + NULL, NULL, + show_grabbing_popup, &data); + } + else + { + show_popup (surface, width, height, layout); + } + } + else + { + reposition_popup (surface, width, height, layout); + } + + while (impl->display_server.xdg_popup && !is_relayout_finished (surface)) + wl_display_dispatch_queue (display_wayland->wl_display, impl->event_queue); + + if (impl->display_server.xdg_popup) + { + gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0); + gdk_surface_invalidate_rect (surface, NULL); + return TRUE; + } + else + { + return FALSE; + } } static void @@ -3697,7 +3897,7 @@ gdk_wayland_surface_class_init (GdkWaylandSurfaceClass *klass) impl_class->lower = gdk_wayland_surface_lower; impl_class->restack_toplevel = gdk_wayland_surface_restack_toplevel; impl_class->toplevel_resize = gdk_wayland_surface_toplevel_resize; - impl_class->move_to_rect = gdk_wayland_surface_move_to_rect; + impl_class->present_popup = gdk_wayland_surface_present_popup; impl_class->get_geometry = gdk_wayland_surface_get_geometry; impl_class->get_root_coords = gdk_wayland_surface_get_root_coords; impl_class->get_device_state = gdk_wayland_surface_get_device_state; diff --git a/gdk/win32/gdksurface-win32.c b/gdk/win32/gdksurface-win32.c index eda9325bd5..d662b1059e 100644 --- a/gdk/win32/gdksurface-win32.c +++ b/gdk/win32/gdksurface-win32.c @@ -1289,18 +1289,21 @@ gdk_win32_surface_move (GdkSurface *surface, } static void -gdk_win32_surface_moved_to_rect (GdkSurface *surface, - GdkRectangle final_rect) +gdk_win32_surface_layout_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { - GdkSurface *toplevel; + GdkRectangle final_rect; int x, y; - if (surface->surface_type == GDK_SURFACE_POPUP) - toplevel = surface->parent; - else - toplevel = surface->transient_for; + gdk_surface_layout_popup_helper (surface, + width, + height, + layout, + &final_rect); - gdk_surface_get_origin (toplevel, &x, &y); + gdk_surface_get_origin (surface->parent, &x, &y); x += final_rect.x; y += final_rect.y; @@ -1308,8 +1311,10 @@ gdk_win32_surface_moved_to_rect (GdkSurface *surface, final_rect.height != surface->height) { gdk_win32_surface_move_resize (surface, - x, y, - final_rect.width, final_rect.height); + x, + y, + final_rect.width, + final_rect.height); } else { @@ -1318,22 +1323,49 @@ gdk_win32_surface_moved_to_rect (GdkSurface *surface, } static void -gdk_win32_surface_move_to_rect (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy) +show_popup (GdkSurface *surface) +{ + gdk_surface_raise (surface); + gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0); + _gdk_surface_update_viewable (surface); + show_window_internal (surface, FALSE, FALSE); + gdk_surface_invalidate_rect (surface, NULL); +} + +static void +show_grabbing_popup (GdkSeat *seat, + GdkSurface *surface, + gpointer user_data) +{ + show_popup (surface); +} + +static gboolean +gdk_win32_surface_present_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { - gdk_surface_move_to_rect_helper (surface, - rect, - rect_anchor, - surface_anchor, - anchor_hints, - rect_anchor_dx, - rect_anchor_dy, - gdk_win32_surface_moved_to_rect); + gdk_win32_surface_layout_popup (surface, width, height, layout); + + if (GDK_SURFACE_IS_MAPPED (surface)) + return TRUE; + + if (surface->autohide) + { + gdk_seat_grab (gdk_display_get_default_seat (surface->display), + surface, + GDK_SEAT_CAPABILITY_ALL, + TRUE, + NULL, NULL, + show_grabbing_popup, NULL); + } + else + { + show_popup (surface); + } + + return GDK_SURFACE_IS_MAPPED (surface); } static void @@ -5161,7 +5193,7 @@ gdk_win32_surface_class_init (GdkWin32SurfaceClass *klass) impl_class->lower = gdk_win32_surface_lower; impl_class->restack_toplevel = gdk_win32_surface_restack_toplevel; impl_class->toplevel_resize = gdk_win32_surface_toplevel_resize; - impl_class->move_to_rect = gdk_win32_surface_move_to_rect; + impl_class->present_popup = gdk_win32_surface_present_popup; impl_class->get_geometry = gdk_win32_surface_get_geometry; impl_class->get_device_state = gdk_surface_win32_get_device_state; impl_class->get_root_coords = gdk_win32_surface_get_root_coords; diff --git a/gdk/x11/gdksurface-x11.c b/gdk/x11/gdksurface-x11.c index b9e122a91a..3ddad661f6 100644 --- a/gdk/x11/gdksurface-x11.c +++ b/gdk/x11/gdksurface-x11.c @@ -1416,18 +1416,21 @@ gdk_x11_surface_move (GdkSurface *surface, } static void -gdk_x11_surface_moved_to_rect (GdkSurface *surface, - GdkRectangle final_rect) +gdk_x11_surface_layout_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) { - GdkSurface *toplevel; + GdkRectangle final_rect; int x, y; - if (surface->surface_type == GDK_SURFACE_POPUP) - toplevel = surface->parent; - else - toplevel = surface->transient_for; + gdk_surface_layout_popup_helper (surface, + width, + height, + layout, + &final_rect); - gdk_surface_get_origin (toplevel, &x, &y); + gdk_surface_get_origin (surface->parent, &x, &y); x += final_rect.x; y += final_rect.y; @@ -1436,8 +1439,10 @@ gdk_x11_surface_moved_to_rect (GdkSurface *surface, { gdk_x11_surface_move_resize (surface, TRUE, - x, y, - final_rect.width, final_rect.height); + x, + y, + final_rect.width, + final_rect.height); } else { @@ -1446,22 +1451,49 @@ gdk_x11_surface_moved_to_rect (GdkSurface *surface, } static void -gdk_x11_surface_move_to_rect (GdkSurface *surface, - const GdkRectangle *rect, - GdkGravity rect_anchor, - GdkGravity surface_anchor, - GdkAnchorHints anchor_hints, - gint rect_anchor_dx, - gint rect_anchor_dy) -{ - gdk_surface_move_to_rect_helper (surface, - rect, - rect_anchor, - surface_anchor, - anchor_hints, - rect_anchor_dx, - rect_anchor_dy, - gdk_x11_surface_moved_to_rect); +show_popup (GdkSurface *surface) +{ + gdk_surface_raise (surface); + gdk_synthesize_surface_state (surface, GDK_SURFACE_STATE_WITHDRAWN, 0); + _gdk_surface_update_viewable (surface); + gdk_x11_surface_show (surface, FALSE); + gdk_surface_invalidate_rect (surface, NULL); +} + +static void +show_grabbing_popup (GdkSeat *seat, + GdkSurface *surface, + gpointer user_data) +{ + show_popup (surface); +} + +static gboolean +gdk_x11_surface_present_popup (GdkSurface *surface, + int width, + int height, + GdkPopupLayout *layout) +{ + gdk_x11_surface_layout_popup (surface, width, height, layout); + + if (GDK_SURFACE_IS_MAPPED (surface)) + return TRUE; + + if (surface->autohide) + { + gdk_seat_grab (gdk_display_get_default_seat (surface->display), + surface, + GDK_SEAT_CAPABILITY_ALL, + TRUE, + NULL, NULL, + show_grabbing_popup, NULL); + } + else + { + show_popup (surface); + } + + return GDK_SURFACE_IS_MAPPED (surface); } static void gdk_x11_surface_restack_toplevel (GdkSurface *surface, @@ -4659,7 +4691,7 @@ gdk_x11_surface_class_init (GdkX11SurfaceClass *klass) impl_class->lower = gdk_x11_surface_lower; impl_class->restack_toplevel = gdk_x11_surface_restack_toplevel; impl_class->toplevel_resize = gdk_x11_surface_toplevel_resize; - impl_class->move_to_rect = gdk_x11_surface_move_to_rect; + impl_class->present_popup = gdk_x11_surface_present_popup; impl_class->get_geometry = gdk_x11_surface_get_geometry; impl_class->get_root_coords = gdk_x11_surface_get_root_coords; impl_class->get_device_state = gdk_x11_surface_get_device_state; diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c index b626a3fbd6..a5285a2766 100644 --- a/gtk/gtkpopover.c +++ b/gtk/gtkpopover.c @@ -155,6 +155,7 @@ typedef struct { GtkCssNode *arrow_node; GskRenderNode *arrow_render_node; + GdkPopupLayout *layout; GdkRectangle final_rect; GtkPositionType final_position; } GtkPopoverPrivate; @@ -221,14 +222,200 @@ gtk_popover_native_get_surface_transform (GtkNative *native, _gtk_css_number_value_get (style->size->padding_top, 100); } +static gboolean +is_gravity_facing_north (GdkGravity gravity) +{ + switch (gravity) + { + case GDK_GRAVITY_NORTH_EAST: + case GDK_GRAVITY_NORTH: + case GDK_GRAVITY_NORTH_WEST: + case GDK_GRAVITY_STATIC: + return TRUE; + case GDK_GRAVITY_SOUTH_WEST: + case GDK_GRAVITY_WEST: + case GDK_GRAVITY_SOUTH_EAST: + case GDK_GRAVITY_EAST: + case GDK_GRAVITY_CENTER: + case GDK_GRAVITY_SOUTH: + return FALSE; + default: + g_assert_not_reached (); + } +} + +static gboolean +is_gravity_facing_south (GdkGravity gravity) +{ + switch (gravity) + { + case GDK_GRAVITY_SOUTH_WEST: + case GDK_GRAVITY_SOUTH_EAST: + case GDK_GRAVITY_SOUTH: + return TRUE; + case GDK_GRAVITY_NORTH_EAST: + case GDK_GRAVITY_NORTH: + case GDK_GRAVITY_NORTH_WEST: + case GDK_GRAVITY_STATIC: + case GDK_GRAVITY_WEST: + case GDK_GRAVITY_EAST: + case GDK_GRAVITY_CENTER: + return FALSE; + default: + g_assert_not_reached (); + } +} + +static gboolean +is_gravity_facing_west (GdkGravity gravity) +{ + switch (gravity) + { + case GDK_GRAVITY_NORTH_WEST: + case GDK_GRAVITY_STATIC: + case GDK_GRAVITY_SOUTH_WEST: + case GDK_GRAVITY_WEST: + return TRUE; + case GDK_GRAVITY_NORTH_EAST: + case GDK_GRAVITY_SOUTH_EAST: + case GDK_GRAVITY_EAST: + case GDK_GRAVITY_NORTH: + case GDK_GRAVITY_CENTER: + case GDK_GRAVITY_SOUTH: + return FALSE; + default: + g_assert_not_reached (); + } +} + +static gboolean +is_gravity_facing_east (GdkGravity gravity) +{ + switch (gravity) + { + case GDK_GRAVITY_NORTH_EAST: + case GDK_GRAVITY_SOUTH_EAST: + case GDK_GRAVITY_EAST: + return TRUE; + case GDK_GRAVITY_NORTH_WEST: + case GDK_GRAVITY_STATIC: + case GDK_GRAVITY_SOUTH_WEST: + case GDK_GRAVITY_WEST: + case GDK_GRAVITY_NORTH: + case GDK_GRAVITY_CENTER: + case GDK_GRAVITY_SOUTH: + return FALSE; + default: + g_assert_not_reached (); + } +} + +static gboolean +did_flip_horizontally (GdkGravity original_gravity, + GdkGravity final_gravity) +{ + g_return_val_if_fail (original_gravity, FALSE); + g_return_val_if_fail (final_gravity, FALSE); + + if (is_gravity_facing_east (original_gravity) && + is_gravity_facing_west (final_gravity)) + return TRUE; + + if (is_gravity_facing_west (original_gravity) && + is_gravity_facing_east (final_gravity)) + return TRUE; + + return FALSE; +} + +static gboolean +did_flip_vertically (GdkGravity original_gravity, + GdkGravity final_gravity) +{ + g_return_val_if_fail (original_gravity, FALSE); + g_return_val_if_fail (final_gravity, FALSE); + + if (is_gravity_facing_north (original_gravity) && + is_gravity_facing_south (final_gravity)) + return TRUE; + + if (is_gravity_facing_south (original_gravity) && + is_gravity_facing_north (final_gravity)) + return TRUE; + + return FALSE; +} + static void -move_to_rect (GtkPopover *popover) +update_popover_layout (GtkPopover *popover, + GdkPopupLayout *layout) +{ + GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + GdkRectangle final_rect; + gboolean flipped_x; + gboolean flipped_y; + + g_clear_pointer (&priv->layout, gdk_popup_layout_unref); + priv->layout = layout; + + final_rect = (GdkRectangle) { + .width = gdk_surface_get_width (priv->surface), + .height = gdk_surface_get_height (priv->surface), + }; + gdk_surface_get_position (priv->surface, + &final_rect.x, + &final_rect.y); + + flipped_x = + did_flip_horizontally (gdk_popup_layout_get_rect_anchor (layout), + gdk_surface_get_popup_rect_anchor (priv->surface)) && + did_flip_horizontally (gdk_popup_layout_get_surface_anchor (layout), + gdk_surface_get_popup_surface_anchor (priv->surface)); + flipped_y = + did_flip_vertically (gdk_popup_layout_get_rect_anchor (layout), + gdk_surface_get_popup_rect_anchor (priv->surface)) && + did_flip_vertically (gdk_popup_layout_get_surface_anchor (layout), + gdk_surface_get_popup_surface_anchor (priv->surface)); + + gtk_widget_allocate (GTK_WIDGET (popover), + gdk_surface_get_width (priv->surface), + gdk_surface_get_height (priv->surface), + -1, NULL); + + priv->final_rect = final_rect; + + switch (priv->position) + { + case GTK_POS_LEFT: + priv->final_position = flipped_x ? GTK_POS_RIGHT : GTK_POS_LEFT; + break; + case GTK_POS_RIGHT: + priv->final_position = flipped_x ? GTK_POS_LEFT : GTK_POS_RIGHT; + break; + case GTK_POS_TOP: + priv->final_position = flipped_y ? GTK_POS_BOTTOM : GTK_POS_TOP; + break; + case GTK_POS_BOTTOM: + priv->final_position = flipped_y ? GTK_POS_TOP : GTK_POS_BOTTOM; + break; + default: + g_assert_not_reached (); + break; + } + + g_clear_pointer (&priv->arrow_render_node, gsk_render_node_unref); + gtk_widget_queue_draw (GTK_WIDGET (popover)); +} + +static GdkPopupLayout * +create_popup_layout (GtkPopover *popover) { GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); GdkRectangle rect; GdkGravity parent_anchor; GdkGravity surface_anchor; GdkAnchorHints anchor_hints; + GdkPopupLayout *layout; gtk_widget_get_surface_allocation (priv->relative_to, &rect); if (priv->has_pointing_to) @@ -341,46 +528,39 @@ move_to_rect (GtkPopover *popover) g_assert_not_reached (); } - gdk_surface_move_to_rect (priv->surface, - &rect, - parent_anchor, - surface_anchor, - anchor_hints, - 0, 0); + layout = gdk_popup_layout_new (&rect, + parent_anchor, + surface_anchor); + gdk_popup_layout_set_anchor_hints (layout, anchor_hints); + + return layout; } static void -gtk_popover_move_resize (GtkPopover *popover) +present_popup (GtkPopover *popover) { GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); GtkRequisition req; + GdkPopupLayout *layout; - if (priv->surface) - { - gtk_widget_get_preferred_size (GTK_WIDGET (popover), NULL, &req); - gdk_surface_resize (priv->surface, req.width, req.height); - move_to_rect (popover); - } + layout = create_popup_layout (popover); + gtk_widget_get_preferred_size (GTK_WIDGET (popover), NULL, &req); + if (gdk_surface_present_popup (priv->surface, + req.width, req.height, + layout)) + update_popover_layout (popover, layout); } static void gtk_popover_native_check_resize (GtkNative *native) { GtkPopover *popover = GTK_POPOVER (native); - GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); GtkWidget *widget = GTK_WIDGET (popover); if (!_gtk_widget_get_alloc_needed (widget)) gtk_widget_ensure_allocate (widget); else if (gtk_widget_get_visible (widget)) - { - gtk_popover_move_resize (popover); - if (priv->surface) - gtk_widget_allocate (GTK_WIDGET (popover), - gdk_surface_get_width (priv->surface), - gdk_surface_get_height (priv->surface), - -1, NULL); - } + present_popup (popover); } @@ -436,7 +616,8 @@ surface_state_changed (GtkWidget *widget) if (changed_mask & GDK_SURFACE_STATE_WITHDRAWN) { - if (priv->state & GDK_SURFACE_STATE_WITHDRAWN) + if (priv->state & GDK_SURFACE_STATE_WITHDRAWN && + gtk_widget_is_visible (widget)) gtk_widget_hide (widget); } } @@ -467,36 +648,13 @@ surface_event (GdkSurface *surface, } static void -surface_moved_to_rect (GdkSurface *surface, - GdkRectangle *flipped_rect, - GdkRectangle *final_rect, - gboolean flipped_x, - gboolean flipped_y, - GtkWidget *widget) +popup_layout_changed (GdkSurface *surface, + GtkWidget *widget) { GtkPopover *popover = GTK_POPOVER (widget); GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); - priv->final_rect = *final_rect; - - switch (priv->position) - { - case GTK_POS_LEFT: - priv->final_position = flipped_x ? GTK_POS_RIGHT : GTK_POS_LEFT; - break; - case GTK_POS_RIGHT: - priv->final_position = flipped_x ? GTK_POS_LEFT : GTK_POS_RIGHT; - break; - case GTK_POS_TOP: - priv->final_position = flipped_y ? GTK_POS_BOTTOM : GTK_POS_TOP; - break; - case GTK_POS_BOTTOM: - priv->final_position = flipped_y ? GTK_POS_TOP : GTK_POS_BOTTOM; - break; - default: - g_assert_not_reached (); - break; - } + update_popover_layout (popover, gdk_popup_layout_ref (priv->layout)); } static void @@ -612,7 +770,7 @@ gtk_popover_realize (GtkWidget *widget) g_signal_connect_swapped (priv->surface, "size-changed", G_CALLBACK (surface_size_changed), widget); g_signal_connect (priv->surface, "render", G_CALLBACK (surface_render), widget); g_signal_connect (priv->surface, "event", G_CALLBACK (surface_event), widget); - g_signal_connect (priv->surface, "moved-to-rect", G_CALLBACK (surface_moved_to_rect), widget); + g_signal_connect (priv->surface, "popup-layout-changed", G_CALLBACK (popup_layout_changed), widget); GTK_WIDGET_CLASS (gtk_popover_parent_class)->realize (widget); @@ -634,7 +792,7 @@ gtk_popover_unrealize (GtkWidget *widget) g_signal_handlers_disconnect_by_func (priv->surface, surface_size_changed, widget); g_signal_handlers_disconnect_by_func (priv->surface, surface_render, widget); g_signal_handlers_disconnect_by_func (priv->surface, surface_event, widget); - g_signal_handlers_disconnect_by_func (priv->surface, surface_moved_to_rect, widget); + g_signal_handlers_disconnect_by_func (priv->surface, popup_layout_changed, widget); gdk_surface_set_widget (priv->surface, NULL); gdk_surface_destroy (priv->surface); g_clear_object (&priv->surface); @@ -648,7 +806,7 @@ gtk_popover_show (GtkWidget *widget) _gtk_widget_set_visible_flag (widget, TRUE); gtk_widget_realize (widget); - gtk_popover_native_check_resize (GTK_NATIVE (widget)); + present_popup (popover); gtk_widget_map (widget); if (priv->autohide) @@ -683,8 +841,8 @@ surface_transform_changed_cb (GtkWidget *widget, GtkPopover *popover = user_data; GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); - move_to_rect (popover); - g_clear_pointer (&priv->arrow_render_node, gsk_render_node_unref); + if (priv->surface && gdk_surface_is_visible (priv->surface)) + present_popup (popover); return G_SOURCE_CONTINUE; } @@ -696,8 +854,7 @@ gtk_popover_map (GtkWidget *widget) GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); GtkWidget *child; - gdk_surface_show (priv->surface); - move_to_rect (popover); + present_popup (popover); priv->surface_transform_changed_cb = gtk_widget_add_surface_transform_changed_callback (priv->relative_to, @@ -755,6 +912,11 @@ gtk_popover_dispose (GObject *object) static void gtk_popover_finalize (GObject *object) { + GtkPopover *popover = GTK_POPOVER (object); + GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + + g_clear_pointer (&priv->layout, gdk_popup_layout_unref); + G_OBJECT_CLASS (gtk_popover_parent_class)->finalize (object); } @@ -1096,8 +1258,6 @@ gtk_popover_size_allocate (GtkWidget *widget, GtkAllocation child_alloc; int tail_height = priv->has_arrow ? TAIL_HEIGHT : 0; - gtk_popover_move_resize (popover); - switch (priv->final_position) { case GTK_POS_TOP: @@ -1414,13 +1574,16 @@ gtk_popover_new (GtkWidget *relative_to) } static void -size_changed (GtkWidget *widget, - int width, - int height, - int baseline, - GtkPopover *popover) +relative_to_size_changed (GtkWidget *widget, + int width, + int height, + int baseline, + GtkPopover *popover) { - gtk_popover_move_resize (popover); + GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + + if (priv->surface && gdk_surface_is_visible (priv->surface)) + present_popup (popover); } void @@ -1487,7 +1650,9 @@ gtk_popover_set_relative_to (GtkPopover *popover, if (priv->relative_to) { - g_signal_handlers_disconnect_by_func (priv->relative_to, size_changed, popover); + g_signal_handlers_disconnect_by_func (priv->relative_to, + relative_to_size_changed, + popover); gtk_widget_unparent (GTK_WIDGET (popover)); } @@ -1496,7 +1661,7 @@ gtk_popover_set_relative_to (GtkPopover *popover, if (priv->relative_to) { g_signal_connect_object (priv->relative_to, "size-allocate", - G_CALLBACK (size_changed), popover, 0); + G_CALLBACK (relative_to_size_changed), popover, 0); gtk_css_node_set_parent (gtk_widget_get_css_node (GTK_WIDGET (popover)), gtk_widget_get_css_node (relative_to)); gtk_widget_set_parent (GTK_WIDGET (popover), relative_to); diff --git a/gtk/gtktooltipwindow.c b/gtk/gtktooltipwindow.c index a80c8034c5..73fc24e625 100644 --- a/gtk/gtktooltipwindow.c +++ b/gtk/gtktooltipwindow.c @@ -110,16 +110,34 @@ gtk_tooltip_window_native_get_surface_transform (GtkNative *native, *y = margin.top + border.top + padding.top; } +static GdkPopupLayout * +create_popup_layout (GtkTooltipWindow *window) +{ + GdkPopupLayout *layout; + + layout = gdk_popup_layout_new (&window->rect, + window->rect_anchor, + window->surface_anchor); + gdk_popup_layout_set_anchor_hints (layout, window->anchor_hints); + gdk_popup_layout_set_offset (layout, window->dx, window->dy); + + return layout; +} + static void -move_to_rect (GtkTooltipWindow *window) +relayout_popup (GtkTooltipWindow *window) { - gdk_surface_move_to_rect (window->surface, - &window->rect, - window->rect_anchor, - window->surface_anchor, - window->anchor_hints, - window->dx, - window->dy); + GdkPopupLayout *layout; + + if (!gtk_widget_get_visible (GTK_WIDGET (window))) + return; + + layout = create_popup_layout (window); + gdk_surface_present_popup (window->surface, + gdk_surface_get_width (window->surface), + gdk_surface_get_height (window->surface), + layout); + gdk_popup_layout_unref (layout); } static void @@ -131,7 +149,8 @@ gtk_tooltip_window_move_resize (GtkTooltipWindow *window) { gtk_widget_get_preferred_size (GTK_WIDGET (window), NULL, &req); gdk_surface_resize (window->surface, req.width, req.height); - move_to_rect (window); + + relayout_popup (window); } } @@ -206,16 +225,6 @@ surface_event (GdkSurface *surface, return TRUE; } -static void -surface_moved_to_rect (GdkSurface *surface, - GdkRectangle *flipped_rect, - GdkRectangle *final_rect, - gboolean flipped_x, - gboolean flipped_y, - GtkWidget *widget) -{ -} - static void gtk_tooltip_window_realize (GtkWidget *widget) { @@ -234,7 +243,6 @@ gtk_tooltip_window_realize (GtkWidget *widget) g_signal_connect_swapped (window->surface, "size-changed", G_CALLBACK (surface_size_changed), widget); g_signal_connect (window->surface, "render", G_CALLBACK (surface_render), widget); g_signal_connect (window->surface, "event", G_CALLBACK (surface_event), widget); - g_signal_connect (window->surface, "moved-to-rect", G_CALLBACK (surface_moved_to_rect), widget); GTK_WIDGET_CLASS (gtk_tooltip_window_parent_class)->realize (widget); @@ -255,7 +263,6 @@ gtk_tooltip_window_unrealize (GtkWidget *widget) g_signal_handlers_disconnect_by_func (window->surface, surface_size_changed, widget); g_signal_handlers_disconnect_by_func (window->surface, surface_render, widget); g_signal_handlers_disconnect_by_func (window->surface, surface_event, widget); - g_signal_handlers_disconnect_by_func (window->surface, surface_moved_to_rect, widget); gdk_surface_set_widget (window->surface, NULL); gdk_surface_destroy (window->surface); g_clear_object (&window->surface); @@ -277,7 +284,7 @@ surface_transform_changed_cb (GtkWidget *widget, { GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (widget); - move_to_rect (window); + relayout_popup (window); return G_SOURCE_CONTINUE; } @@ -287,10 +294,15 @@ static void gtk_tooltip_window_map (GtkWidget *widget) { GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (widget); + GdkPopupLayout *layout; GtkWidget *child; - gdk_surface_show (window->surface); - move_to_rect (window); + layout = create_popup_layout (window); + gdk_surface_present_popup (window->surface, + gdk_surface_get_width (window->surface), + gdk_surface_get_height (window->surface), + layout); + gdk_popup_layout_unref (layout); window->surface_transform_changed_cb = gtk_widget_add_surface_transform_changed_callback (window->relative_to, @@ -599,6 +611,6 @@ gtk_tooltip_window_position (GtkTooltipWindow *window, window->dx = dx; window->dy = dy; - move_to_rect (window); + relayout_popup (window); } -- 2.30.2